Controller system
Introduction#
UI controllers for detection, progress, settings, and results. How each controller talks to state and to the background worker.
Project structure#
The UI controllers live under the assignment-solver extension’s frontend. The entry point initializes adapters, services, state, and controllers, wiring them together for a connected user experience.
graph TB subgraph "UI Controllers" D["Detection Controller<br/>detection.js"] P["Progress Controller<br/>progress.js"] S["Settings Controller<br/>settings.js"] Sol["Solve Controller<br/>solve.js"] end subgraph "State & Elements" ST["State Manager<br/>state.js"] E["DOM Elements Binder<br/>elements.js"] end subgraph "Services & Adapters" R["Runtime Adapter<br/>runtime.js"] MSG["Messages Utility<br/>messages.js"] GS["Storage Service<br/>storage/index.js"] GM["Gemini Service<br/>gemini/index.js"] end subgraph "Entry Point" IDX["UI Entry Point<br/>index.js"] end IDX --> R IDX --> MSG IDX --> GS IDX --> GM IDX --> ST IDX --> E IDX --> D IDX --> P IDX --> S IDX --> Sol Sol --> P Sol --> S Sol --> ST Sol --> GS Sol --> GM Sol --> R Sol --> MSG D --> R D --> MSG S --> GS S --> E P --> E
Core components#
- Detection Controller: Checks current page for assignments, displays assignment info or empty state, and listens for tab updates.
- Progress Controller: Manages status messages, progress bars, step indicators, and visibility of progress sections.
- Settings Controller: Loads and saves API keys and model preferences, manages settings modal UI, and wires event listeners.
- Solve Controller: Orchestrates the full solving flow, including extraction, AI processing, answer filling, optional submission, and results display.
Architecture overview#
The UI entry point initializes adapters and services, then constructs controllers with dependency injection. Controllers communicate via shared state, DOM elements, and runtime messaging.
sequenceDiagram
participant UI as "UI Entry Point<br/>index.js"
participant RT as "Runtime Adapter<br/>runtime.js"
participant MSG as "Messages Utility<br/>messages.js"
participant ST as "State Manager<br/>state.js"
participant GS as "Storage Service<br/>storage/index.js"
participant GM as "Gemini Service<br/>gemini/index.js"
participant DET as "Detection Controller<br/>detection.js"
participant PRG as "Progress Controller<br/>progress.js"
participant SET as "Settings Controller<br/>settings.js"
participant SOL as "Solve Controller<br/>solve.js"
UI->>RT : createRuntimeAdapter()
UI->>MSG : createLogger(), MESSAGE_TYPES
UI->>GS : createStorageService()
UI->>GM : createGeminiService()
UI->>ST : createStateManager()
UI->>DET : createDetectionController({elements, runtime, logger})
UI->>PRG : createProgressController({elements, logger})
UI->>SET : createSettingsController({elements, storage, logger})
UI->>SOL : createSolveController({elements, state, storage, gemini, runtime, progress, settings, logger})
UI->>DET : init()
UI->>SOL : initEventListeners()
UI->>SET : initEventListeners({onSaved})Detailed component analysis#
Detection controller#
Responsibilities:
- Detect assignment presence on the current page.
- Update UI to show assignment info or empty state.
- Listen for tab update events and re-check automatically.
Initialization pattern:
- Receives elements and runtime adapter.
- On init, checks current page immediately and subscribes to runtime messages for tab updates.
Inter-controller communication:
- Listens to runtime messages with type indicating tab updates.
- Uses sendMessageWithRetry to fetch page info from background.
Error handling:
- Catches errors during page info retrieval and falls back to empty state.
Lifecycle:
- checkCurrentPage -> showAssignmentInfo/showEmptyState.
- init triggers initial check and event subscription.
Progress controller#
Responsibilities:
- Update status text and status bar visuals (loading/error states).
- Manage step indicators with active/done/error states.
- Control progress bar count and determinate/indeterminate modes.
- Toggle visibility of progress and results sections.
Initialization pattern:
- Receives elements and optional logger.
- Exposes methods to manipulate DOM elements bound in elements.js.
Inter-controller communication:
- Used by solve controller to reflect stages and progress.
Lifecycle:
- setStatus/setStep/resetSteps/markStepDone/setProgress/setIndeterminate/resetProgress/showProgress/hideProgress.
Settings controller#
Responsibilities:
- Load stored API key and model preferences into form inputs.
- Save API key and preferences to storage.
- Manage settings modal visibility and event listeners.
- Notify parent callback on successful save.
Initialization pattern:
- Receives elements, storage, and optional logger.
- initEventListeners wires click handlers for settings open/close/save.
Inter-controller communication:
- Calls storage service for persistence.
- Invokes callbacks on save completion.
Lifecycle:
- show -> populate inputs -> display modal.
- save -> validate API key -> persist preferences -> return success.
- hide -> animate out and hide.
Solve controller#
Responsibilities:
- Orchestrate the full solving pipeline: extraction, screenshots, AI processing, answer filling, optional submission, and results display.
- Implement recursive splitting to handle MAX_TOKENS errors.
- Integrate with state, progress, settings, storage, runtime, and Gemini service.
Initialization pattern:
- Receives elements, state, storage, gemini, runtime, progress, settings, and optional logger.
- initEventListeners binds solve button to handleSolve.
Inter-controller communication:
- Uses progress controller to reflect stages and progress.
- Uses settings controller to prompt for API key if missing.
- Uses state manager to guard concurrent runs and store extraction results.
- Uses storage service for API key and preferences.
- Uses runtime adapter and messages utility for background communication.
- Uses Gemini service for extraction and solving.
Lifecycle:
- handleSolve orchestrates the full flow with explicit steps and error handling.
- Recursive splitting keeps reliability against token limits.
- Results are rendered into results section with formatted HTML.
Controller factory functions and dependency injection#
- createDetectionController: elements, runtime, logger.
- createProgressController: elements, logger.
- createSettingsController: elements, storage, logger.
- createSolveController: elements, state, storage, gemini, runtime, progress, settings, logger.
- createRuntimeAdapter: wraps browser runtime APIs for messaging and listening.
- createStorageService: wraps storage adapter for API key, preferences, and caches.
- createGeminiService: wraps runtime and API endpoint for extraction and solving.
Dependency injection pattern:
- All controllers accept a deps object with required collaborators.
- The UI entry point constructs adapters/services and injects them into controllers.
- This promotes testability and modularity.
Event handling mechanisms#
- Runtime messaging: sendMessageWithRetry handles transient connection failures and retries.
- DOM events: Settings controller wires click handlers for open/close/save and reasoning selection updates.
- Tab updates: Detection controller listens for runtime messages indicating tab changes.
Controller lifecycle, state integration, and error handling#
- Lifecycle:
- Initialization: UI entry point constructs and wires controllers.
- Interaction: Users trigger actions (solve, settings open/save).
- Completion: Results displayed; state reset on next run.
- State integration:
- State manager tracks isProcessing flag and extraction data.
- Solve controller guards concurrent runs and stores extraction results.
- Error handling:
- sendMessageWithRetry retries on connection errors.
- Solve controller splits work on MAX_TOKENS and aggregates results.
- Errors are surfaced to UI via progress status and debug logging.
Testing patterns and mock implementations#
Recommended patterns:
- Mock runtime adapter to stub sendMessage and onMessage for controller tests.
- Mock storage service to simulate API key and preferences retrieval/persistence.
- Mock Gemini service to return controlled extraction/solve results.
- Mock DOM elements via elements.js to isolate UI logic.
- Use state manager to assert state transitions during tests.
Mock examples (descriptive):
- Runtime adapter mock: replace sendMessage with a spy that resolves to desired responses; onMessage adds a listener that triggers test scenarios.
- Storage service mock: provide getApiKey/getModelPreferences returning known values; save methods record arguments.
- Gemini service mock: resolve extract/solve with deterministic results; optionally reject to simulate errors.
- Elements mock: return element references with innerHTML/className toggles for assertions.
Dependency analysis#
Controllers depend on shared services and adapters. The solve controller has the most dependencies, reflecting its central role.
graph LR SOL["Solve Controller"] DET["Detection Controller"] PRG["Progress Controller"] SET["Settings Controller"] ST["State Manager"] GS["Storage Service"] GM["Gemini Service"] RT["Runtime Adapter"] MSG["Messages Utility"] SOL --> PRG SOL --> SET SOL --> ST SOL --> GS SOL --> GM SOL --> RT SOL --> MSG DET --> RT DET --> MSG SET --> GS PRG --> |updates| UI["DOM Elements"] SET --> |updates| UI
Performance considerations#
- Retry logic: sendMessageWithRetry reduces flakiness on slower browsers by retrying transient connection errors.
- Recursive splitting: solve controller splits HTML and question sets to avoid MAX_TOKENS limits, trading latency for reliability.
- Determinate progress: Progress controller uses determinate progress bars when counts are known; indeterminate mode otherwise.
- Debouncing and pacing: Solve controller introduces small delays between answer fills to avoid overwhelming the target page.
Troubleshooting guide#
Common issues and strategies:
- Background not ready: UI entry point waits for background readiness with exponential backoff; ensure extension reloads if background fails to initialize.
- Connection errors: sendMessageWithRetry detects connection-related errors and retries; inspect last error message for guidance.
- MAX_TOKENS errors: Solve controller splits work recursively; verify model preferences and reduce input size if necessary.
- API key missing: Settings controller prompts for API key; ensure storage contains a valid key before solving.
- Tab switching: Detection controller listens for tab updates; ensure runtime messages are flowing for accurate detection.
Conclusion#
Controllers are orchestration, not DOM. Keep Gemini and storage calls behind services so the UI stays testable.